package
{
import com.societyGames.codeAlong.LoadingText;
import com.societyGames.codeAlong.analytics.Analytics;
import com.societyGames.codeAlong.codeExercise.Compiler;
import com.societyGames.codeAlong.settings.Settings;
import com.societyGames.codeAlong.state.ChangeStateEvent;
import com.societyGames.codeAlong.state.IState;
import com.societyGames.codeAlong.state.StateEnum;
import com.societyGames.codeAlong.state.codeExercise.CodeExerciseState;
import com.societyGames.codeAlong.state.codeExercise.model.ExceptionsData;
import com.societyGames.codeAlong.state.codeExercise.model.FunctionData;
import com.societyGames.codeAlong.state.codeExercise.model.ModuleData;
import com.societyGames.codeAlong.state.codeExercise.model.SharedData;
import com.societyGames.codeAlong.state.codeExercise.model.SyntaxErrorsData;
import com.societyGames.codeAlong.state.codeExercise.model.TermData;
import com.societyGames.codeAlong.state.exerciseSelect.ExerciseSelectState;
import com.societyGames.codeAlong.state.loading.FlushFailedState;
import com.societyGames.codeAlong.state.loading.LoadingState;
import com.societyGames.codeAlong.state.loading.UserDataErrorState;
import com.societyGames.codeAlong.user.PersistentUserData;
import com.societyGames.codeAlong.user.UserDataError;
import com.societyGames.flashForms.BlankDialog;
import com.societyGames.flashForms.CenterContainer;
import com.societyGames.flashForms.DialogManager;
import com.societyGames.flashForms.DirectContainer;
import com.societyGames.flashForms.MouseCursor;
import com.societyGames.loading.ClassXMLLoadStep;
import com.societyGames.loading.LoadQueue;
import com.societyGames.loading.UrlXMLLoadStep;
import com.societyGames.system.FileCache;
import com.societyGames.utility.BitPacker;
import com.societyGames.utility.XMLDictionary;

import flash.display.DisplayObject;
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.events.Event;
import flash.external.ExternalInterface;

[Frame(factoryClass="CodeAlong")]
public class CodeAlong extends Sprite
{
  [Embed(source="/../assets/Modules.xml", mimeType="application/octet-stream")]
  private const _modulesXmlClass:Class;

  [Embed(source="/../assets/SyntaxErrors.xml", mimeType="application/octet-stream")]
  private const _syntaxErrorsXmlClass:Class;

  [Embed(source="/../assets/Exceptions.xml", mimeType="application/octet-stream")]
  private const _exceptionsXmlClass:Class;

  [Embed(source="/../assets/basicSkin/Frame.png", scaleGridTop=3, scaleGridBottom=6, scaleGridLeft=3, scaleGridRight=6)]
  private static const _frameClass:Class; //Sprite

  /*[Embed(source="/assets/Donate.png")]
   private static const _donateButtonClass:Class; //Bitmap*/

  private var _loadQueue:LoadQueue = new LoadQueue();

  private const _swfWidth:int = 1024; //800; //760 for Facebook?
  private const _swfHeight:int = 768; //600;

  private var _settings:Settings;

  private var _currentState:IState;
  private var _currentModuleData:ModuleData;

  private var _fileCache:FileCache = new FileCache();
  private var _moduleDatas:Vector.<ModuleData> = new Vector.<ModuleData>();
  private var _sharedData:SharedData = new SharedData();
  private var _syntaxErrorsData:SyntaxErrorsData;
  private var _exceptionsData:ExceptionsData;
  private var _userData:PersistentUserData;
  private var _loadingText:LoadingText = new LoadingText();
  private var _loadingCenterContainer:CenterContainer = new CenterContainer();

  public function CodeAlong()
  {
    //Get the curtain ready early so if Javascript calls it it will show up later.
    initializeCurtain();
    //Create a basic frame container to attach everything else too.
    initializeInterface();
    //Show the loading graphic early
    showLoadingGraphic();
    //Wait until we're on the stage before doing anything else.
    this.addEventListener(Event.ADDED_TO_STAGE, addedToStageHandler);
  }

  private function addedToStageHandler(event:Event):void
  {
    //Clear the listener
    this.removeEventListener(Event.ADDED_TO_STAGE, addedToStageHandler);
    //Create the settings from FlashVars.
    this._settings = new Settings(getRoot().loaderInfo.parameters);
    //Listen for resizes, which we react to immediately.
    initializeStage();
    //Now start actually loading.
    initializeStaticSystems();
  }

  private function getRoot():DisplayObject
  {
    var displayObject:DisplayObject = this;
    while (displayObject.root == null && displayObject.parent != null)
    {
      displayObject = displayObject.parent;
    }
    return displayObject.root;
  }

  private function initializeStage():void
  {
    this.stage.align = StageAlign.TOP_LEFT;
    this.stage.scaleMode = StageScaleMode.NO_SCALE;
    this.stage.addEventListener(Event.RESIZE, stageResizeHandler);
    stageResizeHandler(null);
  }

  private function stageResizeHandler(event:Event):void
  {
    if (this._mainContainer != null)
    {
      this._mainContainer.width = this.stage.stageWidth;
      this._mainContainer.height = this.stage.stageHeight;
    }
  }

  private function showLoadingGraphic():void
  {
    this._loadingCenterContainer.item = this._loadingText;
    this._frameContainer.item = this._loadingCenterContainer;
  }

  private function hideLoadingGraphic():void
  {
    if (this._frameContainer.item == this._loadingCenterContainer)
    {
      this._frameContainer.item = null;
    }
  }

  private function initializeStaticSystems():void
  {
    //Dialog overlays
    this.addChild(DialogManager.instance); //Set the dialog manager on the top layer.
    //Universal mouse cursor.
    MouseCursor.initialize(this.stage);

    //Load the user data ASAP so we can start analytics
    loadUserData();
  }

  private function userDataInitializeCompleteHandler(event:Event):void
  {
    this._userData.initializeCompletedEventObject.removeEventListener(userDataInitializeCompleteHandler);

    Analytics.initialize(this._userData.userId, this._userData.sharedObjectFlushEnabled, this.loaderInfo);
    Analytics.from(_settings.from != null ? _settings.from : "unknown");

    initializeCompiler();
  }

  private function initializeCompiler():void
  {
    //Load compiler
    Compiler.instance.initializedEventObject.addEventListener(compilerInitializedHandler);
    Compiler.instance.initialize();
  }

  private function compilerInitializedHandler(event:Event):void
  {
    Compiler.instance.initializedEventObject.removeEventListener(compilerInitializedHandler);
    loadXML();
  }

  private function loadXML():void
  {
    if (this._settings.location == null)
    {
      this._loadQueue.add(new ClassXMLLoadStep(_modulesXmlClass, onLoadModuleData));
      this._loadQueue.add(new ClassXMLLoadStep(_syntaxErrorsXmlClass, onLoadSyntaxErrorData));
      this._loadQueue.add(new ClassXMLLoadStep(_exceptionsXmlClass, onLoadExceptionsData));
    }
    else
    {
      this._loadQueue.add(new UrlXMLLoadStep(this._settings.location + "Modules.xml", onLoadModuleData));
      this._loadQueue.add(new UrlXMLLoadStep(this._settings.location + "SyntaxErrors.xml", onLoadSyntaxErrorData));
      this._loadQueue.add(new UrlXMLLoadStep(this._settings.location + "Exceptions.xml", onLoadExceptionsData));
    }

    this._loadQueue.completeEventObject.addEventListener(onLoadQueueComplete);
    this._loadQueue.load();
  }

  private function onLoadModuleData(modulesXml:XML):void
  {
    var xmlDictionary:XMLDictionary = new XMLDictionary(modulesXml.entry);
    var index:int = 0;
    for each (var moduleXml:XML in modulesXml.module)
    {
      var moduleData:ModuleData = initializeModuleData(moduleXml, xmlDictionary, index);
      this._moduleDatas.push(moduleData);
      index++;
    }
  }

  private function initializeModuleData(moduleXml:XML, xmlDictionary:XMLDictionary, index:int):ModuleData
  {
    var moduleData:ModuleData = ModuleData.fromXml(moduleXml, xmlDictionary, index);
    moduleData.functionIndex = _sharedData.functionDatas.length;

    for each (var termData:TermData in moduleData.terms)
    {
      _sharedData.termDatas.push(termData);
    }

    for each (var functionData:FunctionData in moduleData.libraryData.functions)
    {
      if (functionData.isGlobal)
      {
        _sharedData.functionDatas.push(functionData);
      }
    }

    moduleData.termIndex = _sharedData.termDatas.length;
    return moduleData;
  }

  private function onLoadSyntaxErrorData(xml:XML):void
  {
    this._syntaxErrorsData = SyntaxErrorsData.fromXml(xml);
  }

  private function onLoadExceptionsData(xml:XML):void
  {
    this._exceptionsData = ExceptionsData.fromXml(xml);
  }

  private function onLoadQueueComplete(event:Event):void
  {
    this._loadQueue.completeEventObject.removeEventListener(onLoadQueueComplete);
    gotoStateFromUserData();
  }

  private function loadUserData():void //Wait for event
  {
    this._userData = new PersistentUserData();
    this._userData.initializeCompletedEventObject.addEventListener(userDataInitializeCompleteHandler);
    this._userData.initialize();
  }

  private function gotoStateFromUserData():void
  {
    var bestModuleData:ModuleData;

    switch (this._userData.userDataError)
    {
      case UserDataError.EMPTY:
        bestModuleData = this._moduleDatas[0];
        gotoState(StateEnum.GoToExercise, bestModuleData);
        break;
      case UserDataError.SUCCESS:
        bestModuleData = findBestModuleData();
        gotoState(StateEnum.GoToExercise, bestModuleData);
        break;
      case UserDataError.OPEN_FAILED:
        gotoState(StateEnum.ShowExerciseSelect, null);
        break;
      case UserDataError.INCOMPATIBLE_VERSION:
        Analytics.clearAll(); //Dump any old analytics events that are still around.
        gotoState(StateEnum.UserDataError, null);
        break;
      case UserDataError.CORRUPT:
        gotoState(StateEnum.UserDataError, null);
        break;
      case UserDataError.FLUSH_FAILED:
        bestModuleData = findBestModuleData();
        gotoState(StateEnum.UserDataError, bestModuleData);
        break;
    }

    //Start the heart beat so if anything we have infinite loops
    //Heartbeat.initialize();
  }

  private function findBestModuleData():ModuleData
  {
    var bestModuleData:ModuleData = this._moduleDatas[0]; //default to first module.
    var completedExerciseIds:BitPacker = this._userData.completedExerciseIds;
    for each (var moduleData:ModuleData in this._moduleDatas)
    {
      if (moduleData.id == this._userData.currentExerciseId) //The actual exercise that the player last used.
      {
        bestModuleData = moduleData;
        break;
      }
      else if (bestModuleData == null && !completedExerciseIds.getValue(moduleData.id)) //The first exercise the player hasn't beaten.
      {
        bestModuleData = moduleData;
      }
    }

    return bestModuleData;
  }

  private function gotoState(stateEnum:StateEnum, data:Object):void
  {
    //Exit the old state: Do generic clean up.
    if (this._currentState != null)
    {
      this._frameContainer.item = null;
      this._currentState.exit();
      this._currentState.changeStateEventObject.removeEventListener(changeStateHandler);
      this._currentState = null;
    }
    else
    {
      hideLoadingGraphic();
    }

    //Enter new state: Specific setup
    var nextState:IState;
    switch (stateEnum)
    {
      case StateEnum.Loading:
        nextState = new LoadingState(this._swfWidth, this._swfHeight);
        break;
      case StateEnum.UserDataError:
        nextState = new UserDataErrorState(this._userData, this._moduleDatas);
        break;
      case StateEnum.FlushFailed:
        nextState = new FlushFailedState(this._userData, this._currentModuleData, this._moduleDatas, this._sharedData, this._syntaxErrorsData, this._exceptionsData, Compiler.instance, this._fileCache);
        break;
      case StateEnum.ShowExerciseSelect:
        nextState = new ExerciseSelectState(this._userData, this._moduleDatas);
        break;
      case StateEnum.GoToExercise:
        this._currentModuleData = ModuleData(data);
        nextState = new CodeExerciseState(this._userData, this._currentModuleData, this._moduleDatas, this._sharedData, this._syntaxErrorsData, this._exceptionsData, Compiler.instance, this._fileCache);
        break;
      case StateEnum.GoToNextExercise:
        var index:int = this._currentModuleData.index;
        index = int(Math.min(index + 1, _moduleDatas.length - 1)); //Don't go past the last exercise.
        this._currentModuleData = this._moduleDatas[index];
        nextState = new CodeExerciseState(this._userData, this._currentModuleData, this._moduleDatas, this._sharedData, this._syntaxErrorsData, this._exceptionsData, Compiler.instance, this._fileCache);
        break;
    }
    if (nextState != null)
    {
      this._currentState = nextState;
      //Listen to when the current state ends.
      this._currentState.changeStateEventObject.addEventListener(changeStateHandler);
      //Show the current state underneath all the other stuff.
      this._frameContainer.item = this._currentState.displayObject;
    }
  }

  /*private function completeDialogCompleteHandler(event:Event):void
   {
   CompleteDialog(event.target).completeEventObject.removeEventListener(completeDialogCompleteHandler);
   gotoState(StateEnum.ShowExerciseSelect, null);
   }*/

  private function changeStateHandler(changeStateEvent:ChangeStateEvent):void
  {
    gotoState(changeStateEvent.stateEnum, changeStateEvent.data);
  }

  /*private function donateClickHandler(event:Event):void
   {
   //Show the paypal page.
   DonationManager.showDonationPage();
   }*/

  private var _blankDialog:BlankDialog;

  private function initializeCurtain():void
  {
    _blankDialog = new BlankDialog();
    if (ExternalInterface.available)
    {
      ExternalInterface.addCallback("showCurtain", showCurtain);
      ExternalInterface.addCallback("hideCurtain", hideCurtain);
    }
  }

  public function showCurtain():void
  {
    DialogManager.instance.showDialog(_blankDialog);
  }

  public function hideCurtain():void
  {
    DialogManager.instance.hideDialog(_blankDialog);
  }

  private var _mainContainer:DisplayObject;
  //private var _dockContainer:DockContainer;
  private var _frameContainer:DirectContainer;
  //private var _donateButton:ThreeStateButton;

  private function initializeInterface():void
  {
    this._frameContainer = new DirectContainer();
    this._frameContainer.width = this._swfWidth;
    this._frameContainer.height = this._swfHeight;

    /* //The donate button.
     var donateTextField:TextField = SharedTextFields.createSingleLineTextField("Support this project:  ");
     var donateButtonSprite:Sprite = new Sprite();
     donateButtonSprite.addChild(new _donateButtonClass());
     this._donateButton = new ThreeStateButton(donateButtonSprite);
     this._donateButton.buttonClickEventObject.addEventListener(donateClickHandler);

     var donateHorizontalStackContainer:HorizontalStackContainer = new HorizontalStackContainer();
     donateHorizontalStackContainer.addItem(donateTextField);
     donateHorizontalStackContainer.addItem(_donateButton);

     var donateContainer:CenterScaleContainer = new CenterScaleContainer();
     donateContainer.item = donateHorizontalStackContainer;

     this._dockContainer = new DockContainer();
     this._dockContainer.addItem(donateContainer, DockType.bottom, DockUnitType.pixel, donateContainer.height);
     this._dockContainer.addItem(this._frameContainer, DockType.fill, DockUnitType.percent, 1);

     this._mainContainer = this._dockContainer;*/

    this._mainContainer = this._frameContainer; //The real one. Remove this line to test other FlashForms

    //DEBUG
    /*var testContainer:TestCaseButtonsContainer = new TestCaseButtonsContainer();
     var sprite:Sprite = new Sprite();
     sprite.graphics.beginFill(0x000000);
     sprite.graphics.drawRect(0, 0, 50, 50);

     testContainer.addItem(sprite);

     testContainer.spacingWidth = 10;
     testContainer.spacingHeight = 10;*/

    /*var centerContainer:CenterContainer = new CenterContainer();
     centerContainer.item = testContainer;

     this._mainContainer = testContainer; //centerContainer;*/

    //DEBUG

    this.addChildAt(this._mainContainer, 0); //Always the bottom most layer
  }
}
}